package com.acmen.easyapi.factory.impl;

import com.acmen.easyapi.annotation.Module;
import com.acmen.easyapi.annotation.RestApi;
import com.acmen.easyapi.factory.ApiFactory;
import com.acmen.easyapi.model.Api;
import com.acmen.easyapi.model.Dependency;
import com.acmen.easyapi.model.Rpc;
import com.acmen.easyapi.parse.PomParser;
import com.acmen.easyapi.parse.RequestParser;
import com.acmen.easyapi.parse.ResponseParser;
import com.acmen.easyapi.util.*;
import com.acmen.easyapi.util.*;
import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

/**
 * @author acmen
 * @date 2022-03-16
 */
@Component
public class RestApiFactory implements ApiFactory {
    private final Logger log = LoggerFactory.getLogger(RestApiFactory.class);
    public List<Api> apis = new ArrayList<Api>();
    @Resource
    private RequestParser requestParser;
    @Resource
    private ResponseParser responseParser;

    @Override
    public Api getApi(String id) {
        return apis.stream().filter(e->e.getId().equals(id)).findFirst().orElse(null);
    }

    @Override
    public List<Api> createApis(DefaultListableBeanFactory beanFactory) {
         Map<String, InputStream> pomResources = PomParser.getPomResources();  // 获取pom资源
        Map<String, Object> beanMap = beanFactory.getBeansWithAnnotation(Controller.class);  // 获取标记有Controller注解的bean
        for (Entry<String, Object> entry : beanMap.entrySet()) {
            Object bean = entry.getValue();
            bean = AopTargetUtil.getTarget(bean);  // 获取bean的真实目标对象
            RequestMapping requestMapping = bean.getClass().getAnnotation(RequestMapping.class);  // 获取RequestMapping注解
            String basePath = null;
            if (requestMapping != null && requestMapping.value().length > 0) {
                basePath = requestMapping.value()[0];  // 获取基本路径
            }
            Map<Class, Map<String,Method>> rpcInterfacesMap = getRpcInterfacesMap(bean.getClass());  // 获取RPC接口映射
            Module module = bean.getClass().getAnnotation(Module.class);  // 获取Module注解
            Method[] methods = bean.getClass().getMethods();  // 获取bean的所有方法
            for (Method method : methods) {
                RestApi restApi = method.getAnnotation(RestApi.class);  // 获取RestApi注解
                if (restApi == null) {
                    continue;  // 如果没有RestApi注解则跳过
                }
                Api api = createApi(restApi,method, basePath);  // 创建Api对象
                if (api == null) {
                    continue;  // 如果Api对象为空则跳过
                }
                api.setAuthor(restApi.author());  // 设置作者
                if(StringUtil.isNotEmpty(restApi.module())) {  // 如果restApi.module()不为空
                    api.setModule(restApi.module());  // 设置模块
                }else if(module!=null){  // 如果module不为空
                    api.setModule(module.value());  // 设置模块
                }else{
                    api.setModule("未归类模块");  // 设置模块为未归类模块
                }
                if (StringUtil.isNotEmpty(restApi.note())) {
                    String note = FileLoader.fromResource(restApi.note());  // 从资源文件中加载注释
                    if (StringUtil.isEmpty(note)) {
                        note = restApi.note();  // 如果注释为空则直接使用RestApi注解中的注释
                    }
                    api.setNote(note);  // 设置注释
                }
                try {
                    setRpc(pomResources,rpcInterfacesMap,method,api);  // 设置RPC信息
                }catch (Exception e){
                    log.error("解释RPC接口【"+api.getName()+":"+api.getPath()+"】异常！");  // 打印异常信息
                    e.printStackTrace();
                }
                try{
                    api.setNo(restApi.no());  // 设置序号
                    api.setPermission(restApi.permission());  // 设置权限
                    api.setCompleted(restApi.completed());  // 设置是否已完成
                    api.setJavaMethod(method);  // 设置Java方法
                    api.setReqParamNode(requestParser.createParamNode(method));  // 解析请求参数
                    api.setResParamNode(responseParser.createParamNode(method));  // 解析响应参数
                    api.setIdx(restApi.idx());  // 设置索引
                    if(!apis.stream().filter(a->a.getRequestMethod().equals(api.getRequestMethod()) && a.getPath().equals(api.getPath())).findFirst().isPresent()){
                        apis.add(api);  // 如果apis中不存在相同请求方法和路径的接口，则添加到apis中
                    }
                }catch (Exception e){
                    log.error("解释接口【"+api.getName()+":"+api.getPath()+"】异常！");  // 打印异常信息
                    e.printStackTrace();
                }
            }
        }
        apis = apis.stream().sorted(Comparator.comparing(Api::getPath)).collect(Collectors.toList());  // 按照路径排序apis
        return apis;  // 返回apis

    }


    private void setRpc(Map<String, InputStream> pomResources, Map<Class, Map<String,Method>> rpcInterfacesMap, Method method, Api api)
            throws IOException, DocumentException {
         // 判断rpcInterfacesMap是否为空
        if(!rpcInterfacesMap.isEmpty()){
            // 定义rpcInterface变量，并遍历rpcInterfacesMap的键值对
            Class rpcInterface = null;
            for(Entry<Class,Map<String,Method>> e : rpcInterfacesMap.entrySet()){
                // 获取方法的代码
                String code = getMethodCode(method);
                // 判断代码是否在当前rpcInterface的Method中存在
                if(e.getValue().containsKey(code)){
                    rpcInterface = e.getKey();
                    break;
                }
            }
            // 判断rpcInterface是否为空
            if(rpcInterface!=null){
                // 创建Rpc对象
                Rpc rpc = new Rpc();
                // 设置Rpc对象的属性
                rpc.setRpcInterface(rpcInterface.getName());
                rpc.setMethodName(method.getName());
                rpc.setReturnType(method.getGenericReturnType().getTypeName());
                rpc.setParameters(Arrays.asList(ParamNameDiscoverer.getParameterNames(method)));
                // 创建parameterTypes列表，用于存储方法参数的类型
                List<String> parameterTypes = new ArrayList<>();
                // 遍历方法的参数类型，并将类型名称添加到parameterTypes列表中
                for(Type parameterType : method.getGenericParameterTypes()){
                    parameterTypes.add(parameterType.getTypeName());
                }
                rpc.setParameterTypes(parameterTypes);
                // 根据rpcInterface生成依赖关系
                Dependency dependency = PomParser.genDependency(pomResources,rpcInterface);
                rpc.setDependency(dependency);
                // 将Rpc对象添加到api的rpc列表中
                api.setRpc(rpc);
            }
        }

    }

    private Map<Class, Map<String,Method>> getRpcInterfacesMap(Class controller){
        // 获取controller实现的接口
        Class[] interfaces = controller.getInterfaces();
        // 创建一个用于存储接口和方法映射关系的map
        Map<Class, Map<String,Method>> map = new HashMap<>();

        // 遍历接口列表
        for(Class interface1 : interfaces){
            // 创建一个用于存储方法和代码映射关系的map
            Map<String,Method> methodMap = new HashMap();

            // 获取接口上的注解列表
            Annotation[] annotations = interface1.getDeclaredAnnotations();

            // 遍历注解列表
            for(Annotation annotation : annotations){
                // 判断注解的类型是否以"FeignClient"结尾
                if(annotation.annotationType().getName().endsWith("FeignClient")){
                    // 获取接口上的方法列表
                    Method[] methods = interface1.getDeclaredMethods();

                    // 遍历方法列表
                    for(Method method : methods){
                        // 获取方法的代码标识
                        String code = getMethodCode(method);

                        // 将方法和代码映射关系添加到methodMap中
                        methodMap.put(code,method);
                    }

                    // 将接口和方法映射关系添加到map中
                    map.put(interface1,methodMap);
                }
            }
        }

        // 返回map
        return map;

    }


    private String getMethodCode(Method method) {
        String str = method.getName(); // 获取方法的名称
        for(Type type : method.getParameterTypes()){ // 遍历方法的参数类型
            str += type.getTypeName(); // 将参数类型的类型名称拼接到方法名称字符串中
        }
        String code = Md5Util.md5(str); // 对拼接后的字符串进行MD5加密
        return code; // 返回加密后的字符串

    }

    /**
     * 创建API对象
     *
     * @param basePath
     * @param method
     * @return
     */
    private Api createApi(RestApi restApi, Method method, String basePath) {
        // 根据方法找到注解 @RequestMapping
        RequestMapping reqMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
        // 如果 @RequestMapping 为 null，则返回 null
        if(reqMapping==null){
            return null;
        }

        // 获取请求方法，如果 @RequestMapping 中指定了 method，则取第一个 method 的名称，否则默认为 POST
        String requestMethod = reqMapping.method().length > 0 ? reqMapping.method()[0].name() : RequestMethod.POST.name();

        // 创建一个 Api 对象，传入 RESTApi 的名称、请求方法和请求路径
        Api api = new Api(restApi.name(), requestMethod, getRequestPath(basePath, reqMapping.value()));

        // 返回 Api 对象
        return api;

    }

    /**
     * 获取请求路径（数组）
     *
     * @param basePath
     * @param paths
     * @return
     */
    private String getRequestPath(String basePath, String[] paths) {
        if (basePath != null && paths.length != 0) {
            if (basePath.endsWith("/")) {
                basePath = basePath.substring(0, basePath.length() - 1);
            }
            return basePath + paths[0];
        } else if (basePath == null) {
            return paths[0];
        } else {
            return basePath;
        }
    }
    @Override
    public List<Api> getApis() {
        return apis;
    }
}
